package com.htao.train.business.service;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.EnumUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSON;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.github.pagehelper.util.StringUtil;
import com.htao.train.business.domain.*;
import com.htao.train.business.dto.ConfirmOrderMQDto;
import com.htao.train.business.enums.ConfirmOrderStatusEnum;
import com.htao.train.business.enums.RedisKeyPreEnum;
import com.htao.train.business.enums.SeatColEnum;
import com.htao.train.business.enums.SeatTypeEnum;
import com.htao.train.business.req.ConfirmOrderDoReq;
import com.htao.train.business.req.ConfirmOrderTicketReq;
import com.htao.train.common.exception.BusinessException;
import com.htao.train.common.exception.BusinessExceptionEnum;
import com.htao.train.common.resp.PageResp;
import com.htao.train.common.util.SnowUtil;
import com.htao.train.business.mapper.ConfirmOrderMapper;
import com.htao.train.business.req.ConfirmOrderQueryReq;
import com.htao.train.business.resp.ConfirmOrderQueryResp;
import jakarta.annotation.Resource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;

@Service
public class ConfirmOrderService {

    private static final Logger LOG = LoggerFactory.getLogger(ConfirmOrderService.class);

    @Resource
    private ConfirmOrderMapper confirmOrderMapper;
    @Resource
    private DailyTrainTicketService dailyTrainTicketService;
    @Resource
    private DailyTrainCarriageService dailyTrainCarriageService;
    @Resource
    private DailyTrainSeatService  dailyTrainSeatService;
    @Resource
    private AfterConfirmOrderService afterConfirmOrderService;
    @Resource
    private StringRedisTemplate stringRedisTemplate;
    @Resource
    private SkTokenService skTokenService;


    public PageResp<ConfirmOrderQueryResp> queryList(ConfirmOrderQueryReq req) {
        ConfirmOrderExample confirmOrderExample = new ConfirmOrderExample();
        confirmOrderExample.setOrderByClause("id desc");
        ConfirmOrderExample.Criteria criteria = confirmOrderExample.createCriteria();

        LOG.info("查询页码：{}", req.getPage());
        LOG.info("每页条数：{}", req.getSize());
        PageHelper.startPage(req.getPage(), req.getSize());
        List<ConfirmOrder> confirmOrderList = confirmOrderMapper.selectByExample(confirmOrderExample);

        PageInfo<ConfirmOrder> pageInfo = new PageInfo<>(confirmOrderList);
        LOG.info("总行数：{}", pageInfo.getTotal());
        LOG.info("总页数：{}", pageInfo.getPages());

        List<ConfirmOrderQueryResp> list = BeanUtil.copyToList(confirmOrderList, ConfirmOrderQueryResp.class);

        PageResp<ConfirmOrderQueryResp> pageResp = new PageResp<>();
        pageResp.setTotal(pageInfo.getTotal());
        pageResp.setList(list);
        return pageResp;
    }

    public void delete(Long id) {
        confirmOrderMapper.deleteByPrimaryKey(id);
    }

    /**
     * 抢票的降级方法，需包含限流方法的所有参数和BlockException参数
     */
//    public void doConfirmBlock(ConfirmOrderDoReq req, BlockException e) {
//        LOG.info("购票请求被限流：{}", req);
//        throw new BusinessException(BusinessExceptionEnum.CONFIRM_ORDER_FLOW_EXCEPTION);
//    }


    /**
     * 抢票服务
     * @param dto
     */
 //   @SentinelResource(value = "doConfirm", blockHandler = "doConfirmBlock")
    @Async
    public void doConfirm(ConfirmOrderMQDto dto) {
        MDC.put("LOG_ID", dto.getLogId());
        LOG.info("使用自帶异步的方式{}",dto);
        //开始抢座逻辑 先获取锁，防止超卖

        String lockKey = RedisKeyPreEnum.CONFIRM_ORDER+DateUtil.formatDate(dto.getDate())+dto.getTrainCode();
        Boolean aBoolean = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, lockKey, 10, TimeUnit.SECONDS);
        if(BooleanUtil.isTrue(aBoolean)){
            LOG.info("恭喜，抢到锁了！lockKey：{}", lockKey);
        }else {
            LOG.info("没抢到锁，有其它消费线程正在出票，不做任何处理");
            return;
        }

        try {
            //进行批处理 一次处理多个订单
            while (true) {
                // 取确认订单表的记录，同日期车次，状态是I，分页处理，每次取N条
                ConfirmOrderExample confirmOrderExample = new ConfirmOrderExample();
                confirmOrderExample.setOrderByClause("id asc");
                ConfirmOrderExample.Criteria criteria = confirmOrderExample.createCriteria();
                criteria.andDateEqualTo(dto.getDate())
                        .andTrainCodeEqualTo(dto.getTrainCode())
                        .andStatusEqualTo(ConfirmOrderStatusEnum.INIT.getCode());
                PageHelper.startPage(1, 5);
                List<ConfirmOrder> list = confirmOrderMapper.selectByExampleWithBLOBs(confirmOrderExample);

                if (CollUtil.isEmpty(list)) {
                    LOG.info("没有需要处理的订单，结束循环");
                    break;
                } else {
                    LOG.info("本次处理{}条订单", list.size());
                }

                // 一条一条的卖
                list.forEach(confirmOrder -> {
                    try {
                        sell(confirmOrder);
                    } catch (BusinessException e) {
                        if (e.getE().equals(BusinessExceptionEnum.BUSINESS_SEAT_NOT_ENOUGT)) {
                            LOG.info("本订单余票不足，继续售卖下一个订单");
                            confirmOrder.setStatus(ConfirmOrderStatusEnum.EMPTY.getCode());
                            updateStatus(confirmOrder);
                        } else if (e.getE().equals(BusinessExceptionEnum.BUSINESS_CONFIRM_ORDER)) {
                            LOG.info("本订单保存失败，继续售卖下一个订单");
                            confirmOrder.setStatus(ConfirmOrderStatusEnum.FAILURE.getCode());
                            updateStatus(confirmOrder);
                        } else if (e.getE().equals(BusinessExceptionEnum.BUSINESS_SEAT_OTHER)) {
                            LOG.info("本订单出现了不可能存在的座位，继续售卖下一个订单");
                            confirmOrder.setStatus(ConfirmOrderStatusEnum.FAILURE.getCode());
                            updateStatus(confirmOrder);
                        }
                    }
                });
            }
        } finally {
            LOG.info("购票流程结束，释放锁！lockKey：{}", lockKey);
            stringRedisTemplate.delete(lockKey);
        }
    }
    /**
     * 更新状态
     * @param confirmOrder
     */
    public void updateStatus(ConfirmOrder confirmOrder) {
        ConfirmOrder confirmOrderForUpdate = new ConfirmOrder();
        confirmOrderForUpdate.setId(confirmOrder.getId());
        confirmOrderForUpdate.setUpdateTime(new Date());
        confirmOrderForUpdate.setStatus(confirmOrder.getStatus());
        confirmOrderMapper.updateByPrimaryKeySelective(confirmOrderForUpdate);
    }


    private void sell(ConfirmOrder confirmOrder) {
        //显示排队效果
/*        try {
            Thread.sleep(200);
        }catch (InterruptedException e){
            throw new RuntimeException(e);
        }*/

        //先把订单状态修改为处理中
        confirmOrder.setStatus(ConfirmOrderStatusEnum.PENDING.getCode());
        updateStatus(confirmOrder);
        //根据confirmOrder进行购票
        Date date = confirmOrder.getDate();
        String code = confirmOrder.getTrainCode();
        String start = confirmOrder.getStart();
        String end = confirmOrder.getEnd();
        List<ConfirmOrderTicketReq> tickets = JSON.parseArray(confirmOrder.getTickets(), ConfirmOrderTicketReq.class);



        // 查找余票记录 扣库存
        DailyTrainTicket dailyTrainTicket = dailyTrainTicketService.selectByUnique(date, code, start, end);
        LOG.info("当前车辆余票数量为{}",dailyTrainTicket);

        // 扣减余票数量，并判断余票是否足够
        reduceTickets(tickets, dailyTrainTicket);
        // 选座
        //选座的最终结果为
        List<DailyTrainSeat> seatResult = new ArrayList<>();
        //1.1先获取相对位置
        ConfirmOrderTicketReq ticketReq0 = tickets.get(0);
        if(StrUtil.isNotBlank(ticketReq0.getSeat())){
            LOG.info("需要选座 开始选作流程");
            //1.1.1先构造与前端一致的座位信息
            //通过作为类型获取列信息
            List<SeatColEnum> colsByType = SeatColEnum.getColsByType(ticketReq0.getSeatTypeCode());
            //1.1.2构造绝对位置
            List<String> seatModel = new ArrayList<>();
            for (int i = 1; i <= 2; i++) {
                for (SeatColEnum seatColEnum: colsByType) {
                    seatModel.add(seatColEnum.getCode()+i);
                }
            }
            LOG.info("构造的位置为{}",seatModel);
            List<Integer> absSeat = new ArrayList<>();
            for (ConfirmOrderTicketReq confirmOrderTicketReq :tickets){
                int index = seatModel.indexOf(confirmOrderTicketReq.getSeat());
                absSeat.add(index);
            }
            LOG.info("绝对位置为{}",absSeat);
            //1.1.3构造相对位置
            List<Integer> relSeat = new ArrayList<>();
            int first = absSeat.get(0);
            for (int index: absSeat) {
                relSeat.add(index-first);
            }
            LOG.info("相对位置为{}",relSeat);
            getSeat(seatResult,date, code,ticketReq0.getSeatTypeCode()
                    ,ticketReq0.getSeat().split("")[0], relSeat
                    ,dailyTrainTicket.getStartIndex(),dailyTrainTicket.getEndIndex());

        }else{
            LOG.info("本次买票没有选座");
            for (ConfirmOrderTicketReq confirmOrderTicketReq :tickets) {
                getSeat(seatResult,date, code, confirmOrderTicketReq.getSeatTypeCode()
                        , null, null
                        ,dailyTrainTicket.getStartIndex(),dailyTrainTicket.getEndIndex());
            }
        }
        LOG.info("最终选座结果为{}",seatResult);
        try {
            afterConfirmOrderService.afterDoConfirm(dailyTrainTicket,seatResult,tickets,confirmOrder);
        }catch (Exception e) {
            LOG.error("购票信息保存失败",e);
            throw new BusinessException(BusinessExceptionEnum.BUSINESS_CONFIRM_ORDER);
        }
    }

    /**
     * 获取车厢之后再获取座位，有座位则选完，没有选座就一个一个选
     */
    private void getSeat(List<DailyTrainSeat> seatResult,Date date, String trainCode,String SeatType
    ,String column, List<Integer> offsetList,Integer startIndex,Integer endIndex){
        List<DailyTrainCarriage> carriageList = dailyTrainCarriageService.selectBySeatType(date, trainCode, SeatType);
        for (DailyTrainCarriage trainCarriage : carriageList) {
            List<DailyTrainSeat> tempSeatResult = new ArrayList<>();
            Integer index = trainCarriage.getIndex();
            LOG.info("开始从车厢{}选座",index);
            List<DailyTrainSeat> seatList = dailyTrainSeatService.selectByIndex(date, trainCode, index);
            LOG.info("车厢{}座位数为：{}",index,seatList.size());
            for (int i = 0, seatListSize = seatList.size(); i < seatListSize; i++) {
                DailyTrainSeat dailyTrainSeat = seatList.get(i);
                if(StrUtil.isBlank(column)){
                    LOG.info("无需进行选座");
                }else{
                    String col = dailyTrainSeat.getCol();
                    if(!column.equals(col)){
                        LOG.info("座位{}列值{}不对，应该为{}",i+1,col,column);
                        continue;
                    }
                }
                boolean repeatSeat = false;
                for(DailyTrainSeat reSeat:seatResult){
                    if(reSeat.getId().equals(dailyTrainSeat.getId())){
                        repeatSeat=true;
                    }
                }
                if(repeatSeat){
                    LOG.info("座位{}选重了",dailyTrainSeat.getId());
                    continue;
                }
                boolean choose = calSell(dailyTrainSeat, startIndex, endIndex);
                if (choose) {
                    LOG.info("选中了座位");
                    tempSeatResult.add(dailyTrainSeat);
                }else{
                    continue;
                }
                //根据offsetList选择剩下的座位
                boolean isGetAllSeat = true;
                if(CollUtil.isNotEmpty(offsetList)){
                    //从第一个偏移开始
                    for (int j = 1; j < offsetList.size(); j++) {
                        Integer integer = offsetList.get(j);
                        int nextIndex = integer+i;
                        if(nextIndex >= seatListSize){
                            LOG.info("座位{}不可选，已经超出车厢",nextIndex+1);
                            isGetAllSeat = false;
                            break;
                        }
                        //没超车厢的话获取位置 看看卖了吗
                        DailyTrainSeat offsetSeat = seatList.get(nextIndex);
                        boolean chooseNext = calSell(offsetSeat, startIndex, endIndex);
                        //不行的话之前的选中也不行了
                        if(chooseNext){
                            LOG.info("座位{}被选中",offsetSeat.getCarriageSeatIndex());
                            tempSeatResult.add(offsetSeat);
                        }else {
                            LOG.info("偏移座位{}选择失败了",offsetSeat.getCarriageSeatIndex());
                            isGetAllSeat = false;
                            break;
                        }
                    }
                }
                if(!isGetAllSeat){
                    tempSeatResult = new ArrayList<>();
                    continue;
                }
                //保存选好的座位
                seatResult.addAll(tempSeatResult);
                return;
            }
        }
    }

    private boolean calSell(DailyTrainSeat dailyTrainSeat,Integer startIndex,Integer endIndex) {
        String sell = dailyTrainSeat.getSell();
        String sellPart = sell.substring(startIndex,endIndex);
        LOG.info("更改前的SellPart{}",sellPart);
        if(Integer.parseInt(sellPart)>0){
            LOG.info("座位{}在{}到{}没票了",dailyTrainSeat.getCarriageSeatIndex(),startIndex,endIndex);
            return  false;
        }else{
            LOG.info("座位{}在{}到{}可以选中",dailyTrainSeat.getCarriageSeatIndex(),startIndex,endIndex);
            String newSellPart = sellPart.replace('0', '1');
            LOG.info("更改后的newSellPart{}",newSellPart);
            String newSell = sell.substring(0, startIndex) + newSellPart + sell.substring(endIndex);
            LOG.info("售票信息为{}",newSell);
            dailyTrainSeat.setSell(newSell);
            return true;
        }


    }

    private void reduceTickets(List<ConfirmOrderTicketReq> tickets, DailyTrainTicket dailyTrainTicket) {
        for(ConfirmOrderTicketReq ticketReq: tickets){
            String seatTypeCode = ticketReq.getSeatTypeCode();
            SeatTypeEnum seatTypeEnum = EnumUtil.getBy(SeatTypeEnum::getCode, seatTypeCode);
            switch (seatTypeEnum){
                case YDZ -> {
                    int countLeft = dailyTrainTicket.getYdz() - 1;
                    if(countLeft<0){
                        throw new BusinessException(BusinessExceptionEnum.BUSINESS_SEAT_NOT_ENOUGT);
                    }
                    dailyTrainTicket.setYdz(countLeft);
                }
                case EDZ -> {
                    int countLeft = dailyTrainTicket.getEdz() - 1;
                    if(countLeft<0){
                        throw new BusinessException(BusinessExceptionEnum.BUSINESS_SEAT_NOT_ENOUGT);
                    }
                    dailyTrainTicket.setEdz(countLeft);
                }
                case RW -> {
                    int countLeft = dailyTrainTicket.getRw() - 1;
                    if(countLeft<0){
                        throw new BusinessException(BusinessExceptionEnum.BUSINESS_SEAT_NOT_ENOUGT);
                    }
                    dailyTrainTicket.setRw(countLeft);
                }
                case YW -> {
                    int countLeft = dailyTrainTicket.getYw() - 1;
                    if(countLeft<0){
                        throw new BusinessException(BusinessExceptionEnum.BUSINESS_SEAT_NOT_ENOUGT);
                    }
                    dailyTrainTicket.setYw(countLeft);
                }
                default -> {
                    throw new BusinessException(BusinessExceptionEnum.BUSINESS_SEAT_OTHER);
                }
            }
        }
    }

    /**
     * 查询前面有几个人在排队
     * @param id
     */
    public Integer queryLineCount(Long id) {
        ConfirmOrder confirmOrder = confirmOrderMapper.selectByPrimaryKey(id);
        ConfirmOrderStatusEnum statusEnum = EnumUtil.getBy(ConfirmOrderStatusEnum::getCode, confirmOrder.getStatus());
        int result = switch (statusEnum) {
            case PENDING -> 0; // 排队0
            case SUCCESS -> -1; // 成功
            case FAILURE -> -2; // 失败
            case EMPTY -> -3; // 无票
            case CANCEL -> -4; // 取消
            case INIT -> 999; // 需要查表得到实际排队数量
        };

        if (result == 999) {
            // 排在第几位，下面的写法：where a=1 and (b=1 or c=1) 等价于 where (a=1 and b=1) or (a=1 and c=1)
            ConfirmOrderExample confirmOrderExample = new ConfirmOrderExample();
            confirmOrderExample.or().andDateEqualTo(confirmOrder.getDate())
                    .andTrainCodeEqualTo(confirmOrder.getTrainCode())
                    .andCreateTimeLessThan(confirmOrder.getCreateTime())
                    .andStatusEqualTo(ConfirmOrderStatusEnum.INIT.getCode());
            confirmOrderExample.or().andDateEqualTo(confirmOrder.getDate())
                    .andTrainCodeEqualTo(confirmOrder.getTrainCode())
                    .andCreateTimeLessThan(confirmOrder.getCreateTime())
                    .andStatusEqualTo(ConfirmOrderStatusEnum.PENDING.getCode());
            return Math.toIntExact(confirmOrderMapper.countByExample(confirmOrderExample));
        } else {
            return result;
        }
    }

    /**
     * 取消排队，只有I状态才能取消排队，所以按状态更新
     * @param id
     */
    public Integer cancel(Long id) {
        ConfirmOrderExample confirmOrderExample = new ConfirmOrderExample();
        ConfirmOrderExample.Criteria criteria = confirmOrderExample.createCriteria();
        criteria.andIdEqualTo(id).andStatusEqualTo(ConfirmOrderStatusEnum.INIT.getCode());
        ConfirmOrder confirmOrder = new ConfirmOrder();
        confirmOrder.setStatus(ConfirmOrderStatusEnum.CANCEL.getCode());
        return confirmOrderMapper.updateByExampleSelective(confirmOrder, confirmOrderExample);
    }
}
